/*!***************************************************
 * @file: client_management.c
 * @brief: 管理用户的状态、账号信息等底层实现
 * @author: ITHAO 
 * @date: 2018.11.21
 * @note: 这里是底层功能的实现，被client_manage_core.c调用
 ****************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>

#include "client_management.h"
#include "list.h"
#include "debug.h"

/*读写锁，用于处理多线程读写用户账号信息文件的并发*/
static pthread_rwlock_t account_info_rwmtx = PTHREAD_RWLOCK_INITIALIZER;

static bool encipher_or_decipher(char *data, int data_length);
static bool is_password_right(struct login_info *p_login_info);

#define TRUE 1
#define FAIL 0


/**
 * @brief	初始化用户管理模块
 *          初始化在线用户结构体，       
 * @param	p_online_user--在线用户结构体     
 * @return	TRUE                    完成
 *          -MAN_PARAM_NULL_ERR     入口参数指针变量空
 *          -MAN_INIT_RWLOCK_ERR    初始化读写锁失败
 *          -MAN_INIT_MUTEX_ERR     初始化互斥量失败
 */
int online_user_init(struct online_user *p_online_user)
{
    int ret = 0;

    if (!p_online_user) {
        DEBUG("PARAM ERROR!\r\n");
        return (-MAN_PARAM_NULL_ERR);
    }

    /*如果没建文件夹就建文件夹*/
    if (access(LOG_DIR, F_OK) != 0)
        mkdir(LOG_DIR, 0777);

    /*初始化在线用户结构体*/
    p_online_user->online_num = 0;
    init_list_head(&p_online_user->online_list_head);
    ret = pthread_mutex_init(&p_online_user->mutex, NULL);
    if (ret != 0) {
        DEBUG("INIT MUTEX ERROR!\r\n");
        return (-MAN_INIT_MUTEX_ERR);
    }

    return TRUE;
}

/**
 * @brief	destroy用户管理模块       
 * @param	p_online_user--在线用户结构体     
 * @return	TRUE             完成
 *          -MAN_PARAM_NULL_ERR         入口参数指针变量空
 *          -MAN_DESTORY_MUTEX_ERR      destroy互斥量失败
 *          -MAN_DESTORY_RWLOCK_ERR     destroy读写锁失败
 */
int client_management_destroy(struct online_user *p_online_user)
{
    int ret = 0;
    struct list_head *next, *head;
    struct single_online_user *p_single_online_user = NULL;

    if (!p_online_user) {
        DEBUG("PARAM ERROR!\r\n");
        return (-MAN_PARAM_NULL_ERR);
    }

    ret = pthread_mutex_destroy(&p_online_user->mutex);
    if (ret != 0) {
        DEBUG("DESTORY MUTEX ERROR!\r\n");
        return (-MAN_DESTORY_MUTEX_ERR);
    }

    /*清除在线用户*/
    head = &p_online_user->online_list_head;
    list_for_each(next, head) {
        p_single_online_user = list_entry(next, struct single_online_user, list);
        list_del(&p_single_online_user->list);
        next = head;
        free(p_single_online_user);
    }

    /*销毁读写锁*/
    ret = pthread_rwlock_destroy(&account_info_rwmtx);
    if (ret != 0) {
        DEBUG("DESTORY RWLOCK ERROR!\r\n");
        return (-MAN_DESTORY_RWLOCK_ERR);
    }

    return TRUE;
}


/**
 * @brief	用户注册
 *          注册成功，用户信息加载到account.txt
 *          这里只做空判断，是否符合格式交给客户端去做          
 * @param	p_login_info--需要被注册的用户信息结构体      
 * @return	TRUE                        完成
 *          ACCOUNT_EXIXT               账号已经被注册
 * 
 *          -MAN_PARAM_NULL_ERR         入口参数指针变量空
 *          -MAN_OPEN_FILE_FAIL_ERR     打开文件失败
 *          -MAN_WRITE_ERR              写文件错误
 */
int user_register(const struct login_info *p_login_info)
{
    int ret = 0;
    FILE *fp = NULL;
    struct login_info encipher_data;

    if (!p_login_info) {
        DEBUG("PARAM ERROR!\r\n");
        return (-MAN_PARAM_NULL_ERR);
    }

    /*判断账号是否已经被注册*/
    ret = is_account_exist(p_login_info->account, NULL);
    if (ret == TRUE) {
            DEBUG("账号已被注册！\r\n");
            return ACCOUNT_EXIXT;
    }

    /*将要写入文件的内容加密*/
    memcpy(&encipher_data, p_login_info, sizeof(struct login_info));
    ret = encipher_or_decipher((char *)&encipher_data, 
                                sizeof(struct login_info));
    if (ret == 0) {
        DEBUG("用户信息加密失败\r\n");
        return (-MAN_ENCIPHER_FAIL_ERR);
    }

    fp = fopen(LOGIN_INFO_FILE, "ab+");
    if (!fp) {
        DEBUG("打开文件错误!\r\n");
        return (-MAN_OPEN_FILE_FAIL_ERR);
    }

    ret = TRUE;
    /*写锁*/
    /*这里再登陆一次或两次后会发生死锁，找不到原因，先去掉*/
    //pthread_rwlock_wrlock(&account_info_rwmtx);
    /*写用户的昵称、账号、密码*/
    if (fwrite(&encipher_data,
             sizeof(struct login_info), 1, fp) != 1) {
        DEBUG("写入账号信息失败\r\n");
        ret = FAIL;
    }
    pthread_rwlock_unlock(&account_info_rwmtx);

    fclose(fp);
    if (ret == TRUE) {
        LOG("用户%s进行注册", p_login_info->name);
        DEBUG("用户%s注册成功\r\n", p_login_info->name);
    }

    return ret;
}

/**
 * @brief	用户登陆
 *          登陆信息与account.txt里面的信息进行比较
 *          如果登陆成功会将用户名写进p_login_info结构体里面          
 * @param	p_login_info--需要进行登陆的用户信息结构体
 * @return	TRUE                        登陆成功
 *          ACCOUNT_NOT_EXIXT           账号未注册
 *          PASSWORD_INCORRECT          密码错误
 * 
 *          -MAN_PARAM_NULL_ERR         入口参数空
 */
int user_login(struct login_info *p_login_info)
{
    int ret = 0;

    if (!p_login_info) {
        DEBUG("PARAM ERROR!\r\n");
        return (-MAN_PARAM_NULL_ERR);
    }

    /*判断账号是否存在*/
    ret = is_account_exist(p_login_info->account, NULL);
    if (ret == FAIL) {
        DEBUG("账号未注册\r\n");
        return ACCOUNT_NOT_EXIXT;
    }

    /*判断密码是否正确*/
    ret = is_password_right(p_login_info);
    if (ret == FAIL) {
        DEBUG("密码错误！\r\n");
        return PASSWORD_INCORRECT;
    }

    DEBUG("用户%s登陆成功\r\n", p_login_info->name);
    LOG("用户%s进行登陆", p_login_info->name);

    return TRUE;
}

/**
 * @brief	将用户加入到在线用户链表          
 * @param	p_login_info--加入在线链表的用户信息结构体
 *          clientfd--用户对应的通信句柄
 *          p_online_user--在线用户链表头结构体
 * @return	TRUE                        登陆成功
 *          OVER_ONLINE_USER_LIMIT    超过最大人数限制
 *          -MAN_PARAM_NULL_ERR         入口参数为空
 */
int add_online_user(struct login_info *p_login_info, int clientfd,
                    struct online_user *p_online_user)
{
    struct single_online_user *p_single_online_user;

    if (!p_login_info || !p_online_user) {
        DEBUG("PARAM ERROR!\r\n");
        return (-MAN_PARAM_NULL_ERR);
    }

    /*先限制为5k，应该也做不到这么多*/
    if (p_online_user->online_num == MAX_ONLIEN_NUM) {
        DEBUG("超过在线人数限制");
        return OVER_ONLINE_USER_LIMIT;
    }

    /*新建并初始化一个用户结构体来加入在线用户链表*/
    p_single_online_user = (struct single_online_user *)
                             malloc
                             (sizeof(struct single_online_user));
    memset(p_single_online_user->name, '\0', NAME_LENGTH);
    memset(p_single_online_user->account, '\0', ACCOUNT_LENGTH);
    init_list_head(&p_single_online_user->list);

    strcpy(p_single_online_user->name, p_login_info->name);
    strcpy(p_single_online_user->account, p_login_info->account);
    p_single_online_user->clientfd = clientfd;

    pthread_mutex_lock(&p_online_user->mutex);
    p_online_user->online_num++;
    list_add(&p_single_online_user->list,
             &p_online_user->online_list_head);
    pthread_mutex_unlock(&p_online_user->mutex);

    return TRUE;
}


/**
 * @brief	将用户从在线用户链表中移除（注销）
 *          如果account不为空则写入被删除的账号          
 * @param	clientfd--需要进行移除的账号
 *          account--获取被删除的账号
 *          p_online_user--在线用户链表头结构体
 * @return	TRUE                        成功离线
 *          FAIL                        用户不在线
 *          -MAN_PARAM_NULL_ERR         入口参数空
 */
int remove_online_user(int clientfd, char *account,
                       struct online_user *p_online_user)
{
    struct list_head *next, *head;
    struct single_online_user *p_single_online_user = NULL;

    if (!p_online_user || clientfd == -1) {
        DEBUG("PARAM ERROR!\r\n");
        return (-MAN_PARAM_NULL_ERR);
    }

    /*先限制为5k，应该也做不到这么多*/
    if (p_online_user->online_num == 0)
        return FAIL;

    head = &p_online_user->online_list_head;
    pthread_mutex_lock(&p_online_user->mutex);
    list_for_each(next, head) {
        p_single_online_user = list_entry(next, 
                                        struct single_online_user, list);
        if (p_single_online_user->clientfd == clientfd) {
            list_del(&p_single_online_user->list);
            p_online_user->online_num--;
            LOG("用户%s进行注销", p_single_online_user->name);
            /*如果account不为空，写入被注销的账号*/
            if (account)
                strncpy(account,
                        p_single_online_user->account, ACCOUNT_LENGTH);
            free(p_single_online_user);
            pthread_mutex_unlock(&p_online_user->mutex);
            return TRUE;
        }
    }
    pthread_mutex_unlock(&p_online_user->mutex);

    return FAIL;
}

void display_online_user(struct online_user *p_online_user)
{
    if (!p_online_user) {
        DEBUG("PARAM ERROR!\r\n");
        return;
    }

    struct list_head *next, *head;
    struct single_online_user *p_single_online_user = NULL;

    printf("online cilent num is %d\r\n", p_online_user->online_num);
    printf("------------------------\r\n");

    head = &p_online_user->online_list_head;
    pthread_mutex_lock(&p_online_user->mutex);
    list_for_each(next, head) {
        p_single_online_user = list_entry(next, 
                                        struct single_online_user, list);

        printf("CLIENT NAME:    %s\r\n", p_single_online_user->name);
        printf("CLIENT ACCOUNT: %s\r\n", p_single_online_user->account);
        printf("CLIENT FD:      %d\r\n", p_single_online_user->clientfd);
        printf("------------------------\r\n");
    }
    pthread_mutex_unlock(&p_online_user->mutex);
}


/**
 * @brief	判断用户是否在线          
 * @param	account--需要进行判断的用户名
 *          p_online_user--在线用户链表头结构体
 * @return	TRUE                        用户在线
 *          FAIL                        用户不在线
 */
bool is_online_user(const char account[], 
                      struct online_user *p_online_user, int *clientfd)
{   
    struct list_head *next, *head;
    struct single_online_user *p_single_online_user = NULL;

    if (!p_online_user || !account) {
        DEBUG("PARAM ERROR!\r\n");
        return (-MAN_PARAM_NULL_ERR);
    }

    if (p_online_user->online_num == 0)
        return FAIL;

    head = &p_online_user->online_list_head;
    pthread_mutex_lock(&p_online_user->mutex);
    list_for_each(next, head) {
        p_single_online_user = list_entry(next, 
                                    struct single_online_user, list);
        if (!strcmp(p_single_online_user->account, account)) {
            pthread_mutex_unlock(&p_online_user->mutex);
            if (clientfd)
                *clientfd = p_single_online_user->clientfd;
            return TRUE;
        }
    }
    pthread_mutex_unlock(&p_online_user->mutex);

    return FAIL;
}



/**
 * @brief   判断账号是否存在
 * @param   account--装有账号的字符串指针
 * `        get_name--获取名字，空时为不获取
 * @return  TRUE        存在
 *          FAIL        不存在
 */
bool is_account_exist(const char *account, char *get_name)
{
    FILE *fp = NULL;
    struct login_info login_info_data;
    int ret = FAIL;

    if (!account) {
        DEBUG("PARAM ERROR!\r\n");
        return FAIL;
    }

    fp = fopen(LOGIN_INFO_FILE, "ab+");
    if (!fp) {
        DEBUG("文件打开失败\r\n");
        return FAIL;
    }

    /*读锁*/
    pthread_rwlock_rdlock(&account_info_rwmtx);
    while (!feof(fp)) {
        if (fread(&login_info_data, sizeof(struct login_info), 1, fp)) {
            /*将从文件中读取的内容解密*/
            ret = encipher_or_decipher((char *)&login_info_data, 
                                        sizeof(struct login_info));
            if (ret == FAIL) {
                DEBUG("信息解密失败\n");
                break;
            }

            /*这里是防止ret被上面加密函数给干扰了*/
            ret = FAIL; 
            if (!strcmp(login_info_data.account, account)) {
                ret = TRUE;
                break;
            }

        }
    }
    pthread_rwlock_unlock(&account_info_rwmtx);
    fclose(fp);

    if (ret == TRUE && get_name) {
        strncpy(get_name, login_info_data.name, NAME_LENGTH);
    }
    
    return ret;
}


/**
 * @brief   判断对应账号的密码是否正确
 *          判断正确后会将用户名写到p_login_info里面
 * @param   p_login_info：装有用户信息的结构体指针
 * @return  TRUE        成功
 *          FAIL        失败
 */
static bool is_password_right(struct login_info *p_login_info)
{
    FILE *fp = NULL;
    struct login_info login_info_data;
    int ret = FAIL;

    if (!p_login_info) {
        DEBUG("PARAM ERROR!\r\n");
        return FAIL;
    }

    fp = fopen(LOGIN_INFO_FILE, "ab+");
    if (!fp) {
        DEBUG("文件打开失败\r\n");
        return FAIL;
    }

    while (!feof(fp)) {
        pthread_rwlock_rdlock(&account_info_rwmtx);
        if (fread(&login_info_data, sizeof(struct login_info), 1, fp)) {
            pthread_rwlock_unlock(&account_info_rwmtx); 

            /*将从文件中读取的内容解密*/
            ret = encipher_or_decipher((char *)&login_info_data, 
                                        sizeof(struct login_info));
            if (ret == FAIL) {
                DEBUG("信息解密失败\n");
                break;
            }

            /*这里是防止ret被上面加密函数给干扰了*/
            ret = FAIL; 
            if ((!strcmp(login_info_data.account, p_login_info->account)) &&
                (!strcmp(login_info_data.password, p_login_info->password))) {
                ret =TRUE;
                strncpy(p_login_info->name, login_info_data.name, NAME_LENGTH);
                break;
            }
        }
        pthread_rwlock_unlock(&account_info_rwmtx);
    }
    pthread_rwlock_unlock(&account_info_rwmtx);
    fclose(fp);
    
    return ret;
}

/**
 * @brief	对字符串进行加密或解密         
 * @param	data-被加密的数据指针
 *          data_length--数据长度
 * @return	TRUE        成功
 *          FAIL        失败
 */
static bool encipher_or_decipher(char *data, int data_length)
{
	int count;
	
	if (!data) {
        DEBUG("PARAM ERROR!\n");
        return 0;
    }
	
    for(count = 0; count < data_length; count++) {
        if(data[count] != '\0')
			data[count]^='!';
    }

    return 1;
}
